﻿using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Xml;

namespace Oxage.Rasterizer
{
	public partial class XamlConverter
	{
		#region Constants
		private const int dpi = 96;
		#endregion

		#region Overloaded
		public bool XamlToBitmap(string sourceFilename, string targetFilename)
		{
			return XamlToImage<BmpBitmapEncoder>(sourceFilename, targetFilename);
		}

		public bool XamlToJpeg(string sourceFilename, string targetFilename)
		{
			return XamlToImage<JpegBitmapEncoder>(sourceFilename, targetFilename);
		}

		public bool XamlToGif(string sourceFilename, string targetFilename)
		{
			return XamlToImage<GifBitmapEncoder>(sourceFilename, targetFilename);
		}

		public bool XamlToPng(string sourceFilename, string targetFilename)
		{
			return XamlToImage<PngBitmapEncoder>(sourceFilename, targetFilename);
		}

		public bool XamlToTiff(string sourceFilename, string targetFilename)
		{
			return XamlToImage<TiffBitmapEncoder>(sourceFilename, targetFilename);
		}

		public bool XamlToWmp(string sourceFilename, string targetFilename)
		{
			return XamlToImage<WmpBitmapEncoder>(sourceFilename, targetFilename);
		}
		#endregion

		#region Converters
		public bool XamlToImage<TEncoder>(string sourceFilename, string targetFilename) where TEncoder : BitmapEncoder, new()
		{
			return XamlToImage<TEncoder>(sourceFilename, targetFilename, null, null, Stretch.None);
		}

		/// <summary>
		/// Converts a xaml file to a raster image file. Target format is defined with the specified encoder.
		/// </summary>
		/// <param name="sourceFilename">Path to input xaml file.</param>
		/// <param name="destFilename">Path to output raster image file.</param>
		public bool XamlToImage<TEncoder>(string sourceFilename, string targetFilename, int? targetWidth, int? targetHeight, Stretch stretch) where TEncoder : BitmapEncoder, new()
		{
			using (XmlTextReader xmlReader = new XmlTextReader(sourceFilename))
			{
				//Load xaml
				FrameworkElement element = XamlReader.Load(xmlReader) as FrameworkElement;
				return XamlToImage<TEncoder>(element, targetFilename, targetWidth, targetHeight, stretch);
			}
		}

		/// <summary>
		/// Converts a xaml file to a raster image file. Target format is defined with the specified encoder.
		/// </summary>
		/// <param name="sourceFilename">Path to input xaml file.</param>
		/// <param name="destFilename">Path to output raster image file.</param>
		public bool XamlToImage<TEncoder>(FrameworkElement element, string targetFilename, int? targetWidth, int? targetHeight, Stretch stretch) where TEncoder : BitmapEncoder, new()
		{
			return XamlToImage(element, targetFilename, targetWidth, targetHeight, stretch, new TEncoder());
		}

		/// <summary>
		/// Converts a xaml file to a raster image file. Target format is defined with the specified encoder.
		/// </summary>
		/// <param name="sourceFilename">Path to input xaml file.</param>
		/// <param name="destFilename">Path to output raster image file.</param>
		public bool XamlToImage(FrameworkElement element, string targetFilename, int? targetWidth, int? targetHeight, Stretch stretch, BitmapEncoder encoder)
		{
			RenderTargetBitmap renderTargetBitmap = XamlToImage(element, targetWidth, targetHeight, stretch) as RenderTargetBitmap;

			//Encode to the specified format
			encoder.Frames.Add(BitmapFrame.Create(renderTargetBitmap));

			//Save to a file
			using (FileStream fileStream = new FileStream(targetFilename, FileMode.Create))
			{
				encoder.Save(fileStream);
			}

			bool result = File.Exists(targetFilename);

			//Clear garbage collector
			GC.Collect();
			GC.WaitForPendingFinalizers();

			return result;
		}

		/// <summary>
		/// Converts a xaml file to a raster image file. Target format is defined with the specified encoder.
		/// </summary>
		/// <param name="sourceFilename">Path to input xaml file.</param>
		/// <param name="destFilename">Path to output raster image file.</param>
		public ImageSource XamlToImage(FrameworkElement element, int? targetWidth, int? targetHeight, Stretch stretch)
		{
			#region Dimensions
			//Width must be set when alignment is 'Center', 'Right', or 'Stretch'
			if (double.IsNaN(element.Width) && element.HorizontalAlignment != HorizontalAlignment.Left)
			{
				element.HorizontalAlignment = HorizontalAlignment.Left;
			}
			if (double.IsNaN(element.Height) && element.VerticalAlignment != VerticalAlignment.Top)
			{
				element.VerticalAlignment = VerticalAlignment.Top;
			}

			//Arrange max size to get real ActualWidth and ActualHeight values
			element.Arrange(new Rect(0, 0, int.MaxValue, int.MaxValue));

			//Set the target dimensions
			int originalWidth = (int)element.ActualWidth;
			int originalHeight = (int)element.ActualHeight;
			int width = (targetWidth != null && targetWidth.Value > 0 ? targetWidth.Value : originalWidth);
			int height = (targetHeight != null && targetHeight.Value > 0 ? targetHeight.Value : originalHeight);
			#endregion

			#region Stretch
			//If there is no stretch
			if (stretch == Stretch.None || (originalWidth == width && originalHeight == height))
			{
				//Continue...
			}
			else
			{
				//Calculate and apply scale factor
				ScaleTransform scale = new ScaleTransform();
				double factorX = width / originalWidth;
				double factorY = height / originalHeight;
				if (stretch == Stretch.Fill)
				{
					scale.ScaleX = factorX;
					scale.ScaleY = factorY;
				}
				else if (stretch == Stretch.Uniform)
				{
					double factor = (factorX < factorY ? factorX : factorY);
					scale.ScaleX = factor;
					scale.ScaleY = factor;
				}
				else if (stretch == Stretch.UniformToFill)
				{
					double factor = (factorX > factorY ? factorX : factorY);
					scale.ScaleX = factor;
					scale.ScaleY = factor;
				}
				element.RenderTransform = scale;

				double originX = 0.5, originY = 0.5;

				//Get horizontal origin for scale transform
				switch (element.HorizontalAlignment)
				{
					case HorizontalAlignment.Left:
						originX = 0;
						break;

					case HorizontalAlignment.Right:
						originX = 1;
						break;

					default:
					case HorizontalAlignment.Center:
					case HorizontalAlignment.Stretch:
						originX = 0.5;
						break;
				}

				//Get vertical origin for scale transform
				switch (element.VerticalAlignment)
				{
					case VerticalAlignment.Top:
						originY = 0;
						break;

					case VerticalAlignment.Bottom:
						originY = 1;
						break;

					default:
					case VerticalAlignment.Center:
					case VerticalAlignment.Stretch:
						originY = 0.5;
						break;
				}

				element.RenderTransformOrigin = new Point(originX, originY);
			}
			#endregion

			//Arrange to target size
			element.Arrange(new Rect(0, 0, width, height));

			//Render to bitmap
			RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap(width, height, dpi, dpi, PixelFormats.Default);
			renderTargetBitmap.Render(element);

			return renderTargetBitmap;
		}

		/// <summary>
		/// Converts a xaml file to a raster image file. Target format is defined with the specified encoder.
		/// </summary>
		/// <param name="sourceFilename">Path to input xaml file.</param>
		/// <param name="destFilename">Path to output raster image file.</param>
		public bool XamlToImage(VisualBrush brush, string targetFilename, int targetWidth, int targetHeight, Stretch stretch, BitmapEncoder encoder)
		{
			RenderTargetBitmap renderTargetBitmap = XamlToImage(brush, targetWidth, targetHeight, stretch) as RenderTargetBitmap;

			//Encode to the specified format
			encoder.Frames.Add(BitmapFrame.Create(renderTargetBitmap));

			//Save to a file
			using (FileStream fileStream = new FileStream(targetFilename, FileMode.Create))
			{
				encoder.Save(fileStream);
			}

			bool result = File.Exists(targetFilename);

			//Clear garbage collector
			GC.Collect();
			GC.WaitForPendingFinalizers();

			return result;
		}

		/// <summary>
		/// Converts a xaml file to a raster image file. Target format is defined with the specified encoder.
		/// </summary>
		/// <param name="sourceFilename">Path to input xaml file.</param>
		/// <param name="destFilename">Path to output raster image file.</param>
		public ImageSource XamlToImage(VisualBrush brush, int targetWidth, int targetHeight, Stretch stretch)
		{
			brush.Stretch = stretch;

			Grid element = new Grid();
			element.Width = targetWidth;
			element.Height = targetHeight;
			element.Background = brush;

			//Arrange to target size
			element.Arrange(new Rect(0, 0, targetWidth, targetHeight));

			//Render to bitmap
			RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap(targetWidth, targetHeight, dpi, dpi, PixelFormats.Default);
			renderTargetBitmap.Render(element);

			return renderTargetBitmap;
		}
		#endregion
	}
}
